import { memo, useState } from 'react';
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
import Viewer from 'react-viewer';
import remarkGfm from 'remark-gfm';

import { Box, Link, Stack, Typography } from '@mui/material';

import Code from 'components/atoms/Code';
import ElementRef from 'components/atoms/element/ref';

import { IMessageElement } from 'state/element';

import InlinedElements from './inlined';


import { loadingState } from 'state/chat';

import { useSetRecoilState } from 'recoil';

interface Props {
  id?: string;
  content?: string;
  elements: IMessageElement[];
  language?: string;
  authorIsUser?: boolean;
}

const isForIdMatch = (
  id: string | number | undefined,
  forIds: string[] | undefined
) => {
  if (!forIds || !forIds.length || !id) {
    return false;
  }

  return forIds.includes(id.toString());
};

const isGlobalMatch = (forIds: string[] | undefined) => {
  return !forIds || !forIds.length;
};

function escapeRegExp(string: string) {
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function prepareContent({ id, elements, content, language }: Props) {
  const elementNames = elements.map((e) => escapeRegExp(e.name));

  // Sort by descending length to avoid matching substrings
  elementNames.sort((a, b) => b.length - a.length);

  const elementRegexp = elementNames.length
    ? new RegExp(`(${elementNames.join('|')})`, 'g')
    : undefined;

  let preparedContent = content ? content.trim() : '';
  const inlinedElements = elements.filter(
    (e) => isForIdMatch(id, e?.forIds) && e.display === 'inline'
  );
  const refElements: IMessageElement[] = [];

  if (elementRegexp) {
    preparedContent = preparedContent.replaceAll(elementRegexp, (match) => {
      const element = elements.find((e) => {
        const nameMatch = e.name === match;
        const scopeMatch =
          isGlobalMatch(e?.forIds) || isForIdMatch(id, e?.forIds);
        return nameMatch && scopeMatch;
      });
      const foundElement = !!element;
      const inlined = element?.display === 'inline';
      if (!foundElement) {
        // Element reference does not exist, return plain text
        return match;
      } else if (inlined) {
        // If element is inlined, add it to the list and return plain text
        if (inlinedElements.indexOf(element) === -1) {
          inlinedElements.push(element);
        }
        return match;
      } else {
        // Element is a reference, add it to the list and return link
        refElements.push(element);
        // spaces break markdown links. The address in the link is not used anyway
        return `[${match}](${match.replaceAll(' ', '_')})`;
      }
    });
  }

  if (language) {
    preparedContent = `\`\`\`${language}\n${preparedContent}\n\`\`\``;
  }
  return {
    preparedContent,
    inlinedElements,
    refElements
  };
}

export default memo(function MessageContent({
  id,
  content,
  elements,
  language,
  authorIsUser
}: Props) {
  const setLoading = useSetRecoilState(loadingState);
  const { preparedContent, inlinedElements, refElements } = prepareContent({
    id,
    content,
    language,
    elements
  });
  const [open, setOpen] = useState(false);
  const [previewUrl, setPreviewUrl] = useState('');
  const imgPreview = (props: any) => {
    setPreviewUrl(props.src);
    setOpen(true);
  };
  if (!preparedContent) return null;

  return (
    <Stack width="100%">
      <Stack direction="row">
        <Typography
          sx={{
            width: '100%',
            minHeight: '20px',
            fontSize: '1rem',
            lineHeight: '1.5rem',
            fontFamily: 'Inter',
            fontWeight: authorIsUser ? 500 : 300
          }}
          component="div"
        >
          <ReactMarkdown
            remarkPlugins={[remarkGfm]}
            className="markdown-body"
            components={{
              a({ children, ...props }) {
                const name = children[0] as string;
                const element = refElements.find((e) => e.name === name);

                if (element) {
                  return <ElementRef element={element} />;
                } else {
                  return (
                    <Link {...props} target="_blank">
                      {children}
                    </Link>
                  );
                }
              },
              code({ ...props }) {
                return <Code {...props} />;
              },
              tr({ children }) {
                return (
                  <Box
                    component="tr"
                    sx={{
                      color: 'text.primary',
                      backgroundColor: (theme) => {
                        if (theme.palette.mode === 'light') {
                          return '#fff';
                        } else {
                          return theme.palette.background.default;
                        }
                      }
                    }}
                  >
                    {children}
                  </Box>
                );
              },
              th({ children }) {
                return (
                  <Box
                    component="th"
                    sx={{
                      border: (theme) => {
                        if (theme.palette.mode === 'light') {
                          return '1px solid #d0d7de;';
                        } else {
                          return `1px solid ${theme.palette.divider}`;
                        }
                      }
                    }}
                  >
                    {children}
                  </Box>
                );
              },
              td({ children }) {
                return (
                  <Box
                    component="td"
                    sx={{
                      border: (theme) => {
                        if (theme.palette.mode === 'light') {
                          return '1px solid #d0d7de;';
                        } else {
                          return `1px solid ${theme.palette.divider}`;
                        }
                      }
                    }}
                  >
                    {children}
                  </Box>
                );
              },
              img({ ...props }) {
                return (
                  <Box
                    component="img"
                    onClick={() => imgPreview(props)}
                    style={{ cursor: 'pointer' }}
                    {...props}
                  ></Box>
                );
              }
            }}
          >
            {preparedContent}
          </ReactMarkdown>
        </Typography>
      </Stack>
      <InlinedElements elements={inlinedElements} />
      <Viewer
        visible={open}
        noNavbar={true}
        showTotal={false}
        noImgDetails={true}
        customToolbar={(toolbars) => {
          return toolbars.concat([
            {
              key: 'download-img',
              render: (
                <div>
                  <i className="react-viewer-icon react-viewer-icon-download"></i>
                </div>
              ),
              onClick: (activeImage) => {
                const link = document.createElement('a');
                link.href = activeImage.src;
                link.target = '_blank';
                link.download = `images.png`;
                link.style.display = 'none';
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
              }
            }
          ]);
        }}
        onClose={() => {
          setOpen(false);
        }}
        images={[{ src: previewUrl, downloadUrl: previewUrl }]}
      />
    </Stack>
  );
});
